#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Jest 插件
用于执行 JavaScript/TypeScript 单元测试
"""

import os
import subprocess
import json
from typing import Dict, List, Any


class JestPlugin:
    """Jest 插件"""
    
    def __init__(self):
        self.name = "jest"
        self.description = "JavaScript/TypeScript 单元测试"
    
    def execute(self, config: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
        """执行 Jest 测试"""
        try:
            # 检查是否存在 package.json
            if not os.path.exists('package.json'):
                return {
                    'success': True,
                    'output': '未找到 package.json，跳过 Jest 测试',
                    'exit_code': 0
                }
            
            # 获取配置参数
            test_command = config.get('command', 'npm test')
            test_files = config.get('files', [])
            test_pattern = config.get('pattern', '')
            coverage = config.get('coverage', False)
            watch = config.get('watch', False)
            verbose = config.get('verbose', False)
            
            # 构建命令
            if test_command.startswith('npm'):
                cmd = test_command.split()
            else:
                cmd = ['npx', 'jest']
            
            # 添加测试文件
            if test_files:
                cmd.extend(test_files)
            elif test_pattern:
                cmd.extend(['--testPathPattern', test_pattern])
            
            # 添加覆盖率选项
            if coverage:
                cmd.append('--coverage')
            
            # 添加详细输出
            if verbose:
                cmd.append('--verbose')
            
            # 非监视模式
            if not watch:
                cmd.append('--passWithNoTests')
            
            # 执行命令
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=600  # 10分钟超时
            )
            
            # 解析结果
            output_lines = result.stdout.split('\n') if result.stdout else []
            error_lines = result.stderr.split('\n') if result.stderr else []
            
            # 检查测试结果
            test_passed = result.returncode == 0
            
            if test_passed:
                # 解析测试结果
                test_summary = self._parse_test_summary(output_lines)
                return {
                    'success': True,
                    'output': self._format_success_output(test_summary),
                    'exit_code': 0
                }
            else:
                # 解析失败信息
                failure_info = self._parse_failure_info(output_lines, error_lines)
                return {
                    'success': False,
                    'output': self._format_failure_output(failure_info),
                    'error': '\n'.join(error_lines),
                    'exit_code': result.returncode
                }
                
        except subprocess.TimeoutExpired:
            return {
                'success': False,
                'output': '',
                'error': 'Jest 测试执行超时',
                'exit_code': 1
            }
        except Exception as e:
            return {
                'success': False,
                'output': '',
                'error': f'Jest 测试执行失败: {e}',
                'exit_code': 1
            }
    
    def _parse_test_summary(self, output_lines: List[str]) -> Dict[str, Any]:
        """解析测试摘要"""
        summary = {
            'total_tests': 0,
            'passed': 0,
            'failed': 0,
            'skipped': 0,
            'duration': 0,
            'coverage': {}
        }
        
        for line in output_lines:
            line = line.strip()
            if not line:
                continue
            
            # 解析测试统计
            if 'Tests:' in line:
                # 格式: Tests: 5 passed, 5 total
                parts = line.split(':')[1].strip().split(',')
                for part in parts:
                    part = part.strip()
                    if 'passed' in part:
                        summary['passed'] = int(part.split()[0])
                    elif 'failed' in part:
                        summary['failed'] = int(part.split()[0])
                    elif 'skipped' in part:
                        summary['skipped'] = int(part.split()[0])
                    elif 'total' in part:
                        summary['total_tests'] = int(part.split()[0])
            
            # 解析执行时间
            elif 'Time:' in line:
                # 格式: Time: 1.234s
                time_str = line.split(':')[1].strip().replace('s', '')
                try:
                    summary['duration'] = float(time_str)
                except ValueError:
                    pass
            
            # 解析覆盖率
            elif 'All files' in line and '|' in line:
                # 覆盖率行格式
                coverage_data = self._parse_coverage_line(line)
                if coverage_data:
                    summary['coverage'] = coverage_data
        
        return summary
    
    def _parse_failure_info(self, output_lines: List[str], error_lines: List[str]) -> Dict[str, Any]:
        """解析失败信息"""
        failures = []
        current_failure = {}
        
        for line in output_lines + error_lines:
            line = line.strip()
            if not line:
                continue
            
            # 检测测试失败
            if 'FAIL' in line and 'test' in line.lower():
                if current_failure:
                    failures.append(current_failure)
                current_failure = {
                    'test_name': line,
                    'error_message': '',
                    'stack_trace': []
                }
            elif current_failure and ('Expected' in line or 'Received' in line):
                current_failure['error_message'] += line + '\n'
            elif current_failure and ('at ' in line or 'Error:' in line):
                current_failure['stack_trace'].append(line)
        
        if current_failure:
            failures.append(current_failure)
        
        return {
            'failures': failures,
            'total_failures': len(failures)
        }
    
    def _parse_coverage_line(self, line: str) -> Dict[str, Any]:
        """解析覆盖率行"""
        try:
            # 覆盖率行格式: All files | 85.71 | 80.00 | 90.00 | 85.71 |
            parts = line.split('|')
            if len(parts) >= 5:
                return {
                    'statements': float(parts[1].strip()),
                    'branches': float(parts[2].strip()),
                    'functions': float(parts[3].strip()),
                    'lines': float(parts[4].strip())
                }
        except (ValueError, IndexError):
            pass
        return {}
    
    def _format_success_output(self, summary: Dict[str, Any]) -> str:
        """格式化成功输出"""
        output = []
        output.append("Jest 测试通过!")
        output.append("=" * 30)
        
        if summary['total_tests'] > 0:
            output.append(f"总测试数: {summary['total_tests']}")
            output.append(f"通过: {summary['passed']}")
            if summary['failed'] > 0:
                output.append(f"失败: {summary['failed']}")
            if summary['skipped'] > 0:
                output.append(f"跳过: {summary['skipped']}")
            
            if summary['duration'] > 0:
                output.append(f"执行时间: {summary['duration']:.2f}s")
            
            # 显示覆盖率
            if summary['coverage']:
                coverage = summary['coverage']
                output.append("\n覆盖率:")
                output.append(f"  语句: {coverage.get('statements', 0):.1f}%")
                output.append(f"  分支: {coverage.get('branches', 0):.1f}%")
                output.append(f"  函数: {coverage.get('functions', 0):.1f}%")
                output.append(f"  行数: {coverage.get('lines', 0):.1f}%")
        else:
            output.append("没有找到测试文件")
        
        return '\n'.join(output)
    
    def _format_failure_output(self, failure_info: Dict[str, Any]) -> str:
        """格式化失败输出"""
        output = []
        output.append("Jest 测试失败!")
        output.append("=" * 30)
        
        failures = failure_info.get('failures', [])
        total_failures = failure_info.get('total_failures', 0)
        
        output.append(f"失败测试数: {total_failures}")
        output.append("")
        
        for i, failure in enumerate(failures, 1):
            output.append(f"失败测试 {i}: {failure.get('test_name', 'Unknown')}")
            if failure.get('error_message'):
                output.append("错误信息:")
                output.append(failure['error_message'])
            if failure.get('stack_trace'):
                output.append("堆栈跟踪:")
                for trace in failure['stack_trace'][:5]:  # 只显示前5行
                    output.append(f"  {trace}")
            output.append("")
        
        return '\n'.join(output)
    
    def check_installation(self) -> bool:
        """检查 Jest 是否已安装"""
        try:
            # 检查 package.json 中是否有 jest
            if os.path.exists('package.json'):
                with open('package.json', 'r', encoding='utf-8') as f:
                    package_data = json.load(f)
                    dev_deps = package_data.get('devDependencies', {})
                    deps = package_data.get('dependencies', {})
                    return 'jest' in dev_deps or 'jest' in deps
            
            # 检查全局安装
            result = subprocess.run(
                ['npx', 'jest', '--version'],
                capture_output=True,
                text=True,
                timeout=10
            )
            return result.returncode == 0
        except Exception:
            return False
    
    def install_jest(self, project_dir: str = None) -> bool:
        """安装 Jest"""
        try:
            cmd = ['npm', 'install', '--save-dev', 'jest', '@types/jest']
            if project_dir:
                result = subprocess.run(cmd, cwd=project_dir, timeout=120)
            else:
                result = subprocess.run(cmd, timeout=120)
            
            return result.returncode == 0
        except Exception:
            return False
    
    def create_config(self, config_file: str = 'jest.config.js') -> bool:
        """创建 Jest 配置文件"""
        try:
            config_content = '''module.exports = {
  testEnvironment: 'node',
  testMatch: [
    '**/__tests__/**/*.js',
    '**/?(*.)+(spec|test).js'
  ],
  collectCoverageFrom: [
    'src/**/*.js',
    '!src/**/*.test.js',
    '!src/**/*.spec.js'
  ],
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html']
};
'''
            
            with open(config_file, 'w', encoding='utf-8') as f:
                f.write(config_content)
            
            return True
        except Exception:
            return False


# 插件接口
def execute(config: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
    """插件执行接口"""
    plugin = JestPlugin()
    return plugin.execute(config, context)


if __name__ == "__main__":
    # 测试插件
    plugin = JestPlugin()
    
    # 测试配置
    test_config = {
        'command': 'npm test',
        'coverage': True,
        'verbose': True
    }
    
    test_context = {
        'staged_files': ['test.js']
    }
    
    result = plugin.execute(test_config, test_context)
    print(f"执行结果: {result}")
